using System;
using System.Collections;
using System.Collections.Specialized;
using System.Reflection;
using System.Text;
using System.IO;
using System.Diagnostics;

namespace Copperline.Escriba
{
	/// <summary>
	/// Formats log events as text strings.
	/// </summary>
	public class TextFormatter : IFormatter
	{
		private string sSep;
		private bool bAlignMultiple;
		private ArrayList aFieldSequence;

		/// <summary>
		/// Creates an instance of the class <c>TextFormatter</c>
		/// with given style, severity, and triggering.
		/// </summary>
		public TextFormatter()
		{
			Init();
		}

		private void Init()
		{
			aFieldSequence = new ArrayList();
			Type type = Type.GetType("Copperline.Escriba.LogEntry");
			SetFieldSequence(
				new FieldFormatter( type.GetProperty("TimeStamp"), LogEntry.TimeStampFormat ),
				new FieldFormatter( type.GetProperty("SeverityName"), LogEntry.SeverityFormat ) );

			AlignMultiple = true;
			FieldSeparator = " ";
		}

		/// <summary>
		/// Sets the sequence of fields that will be emitted in a log entry
		/// </summary>
		/// <param name="aFields">an array of <c>EField</c> enumerated types. The first element
		/// in the array defines the first field that will be emitted, and so on.</param>
		/// <remarks>
		/// The enumeration <c>EField.entry</c> is always emitted last, regardless of its position
		/// declared in this sequence.
		/// </remarks>
		public void SetFieldSequence( params IField[] aFields )
		{
			aFieldSequence.Clear();
			aFieldSequence.AddRange( aFields );
		}

		/// <summary>
		/// Sets or gets a boolean flag that determines whether log entries that span multiple lines
		/// are aligned (indented) to match the column position of the first line
		/// </summary>
		/// <remarks>Defaults to true: multiple lines are indented to match the beginning of the first
		/// line.</remarks>
		public bool AlignMultiple
		{
			get { return bAlignMultiple; }
			set { bAlignMultiple = value; }
		}

		/// <summary>
		/// Gets or gets a string that will be used to separate fields in an output line.
		/// </summary>
		/// <remarks>Usually the string consists of a single character surrounded by spaces,
		/// ie: " * "</remarks>
		public string FieldSeparator
		{
			get { return sSep; }
			set { sSep = value; }
		}

		/// <summary>
		/// Formats a <c>LogEntry</c> object according to the formatting configuration
		/// </summary>
		/// <param name="le">The <c>LogEntry</c> object to write.</param>
		/// <returns>an array of strings representing the formatted log entry</returns>
		public string[] Format( LogEntry le )
		{
			if( le == null )
				return null;

			StringBuilder sLine = new StringBuilder(120);
			foreach( FieldFormatter ff in aFieldSequence )
			{
				string sValue = ff.Value( le );
				if( sValue != null )
				{
					sLine.AppendFormat( ff.Format, sValue );
					sLine.Append( FieldSeparator );
				}
			}

			string sTerm = String.Empty;
			string sBlanks = String.Empty;
			if( AlignMultiple )
			{
				sTerm = FieldSeparator.TrimStart(' ');
				sBlanks = new string( ' ', sLine.Length - sTerm.Length );
			}

			string sMsg = le.Entry.Replace( "\r\n", "\n" ).TrimStart( ' ', '\t', '\n' );
			string[] saMsg = sMsg.Split( '\n' );
			if( saMsg.Length == 0 )
				return new string[] { sLine.ToString() };

			sLine.Append( saMsg[0] );
			Array asFormatted = Array.CreateInstance( typeof(string), saMsg.Length );
			asFormatted.SetValue( sLine.ToString(), 0 );

			if( saMsg.Length > 1 )
			{
				for( int i=1; i<saMsg.Length; i++ )
				{
					StringBuilder sLine2 = new StringBuilder( sBlanks.Length + sTerm.Length + saMsg[i].Length );
					sLine2.Append( sBlanks );
					sLine2.Append( sTerm );
					sLine2.Append( saMsg[i] );
					asFormatted.SetValue( sLine2.ToString(), i );
				}
			}

			return (string[])asFormatted;
		}
	}
}
